﻿package  plugins.mstandio.UniversalMap {
	
	/**
	 * UniversalMap class reads settings from xml file, and builds structures containing those settings
	 * Creates base interface and adds hide/show functionalities
	 * Reacts to space changes, gets changes data and sends it down to through MapViewer to NavigationPoint
	 * @author mstandio
	 */ 
			
    import flash.system.ApplicationDomain;	
	import zephyr.BroadcastEvent;
	import gs.TweenLite;
	    
    import flash.display.Sprite;
	import flash.display.StageDisplayState;
	import flash.events.Event; 
	import flash.display.Bitmap;
	import flash.events.MouseEvent; 
	import flash.net.URLRequest;	
		
	import plugins.mstandio.UniversalMap.navigation.*;
	import plugins.mstandio.utils.combobox.ComboBox;
	import plugins.mstandio.utils.combobox.ComboBoxStyle;
	import plugins.mstandio.utils.combobox.ComboBoxEvent;	
					
	public class UniversalMap extends Sprite {
		
		[Embed(source="images/interface/universalMap_icon.png")]
			public var Bitmap_universalMapIcon:Class;
		[Embed(source="images/interface/universalMap_close3.png")]
			public var Bitmap_universalMapClose:Class;
		
		private var mapWindowWidth:Number;
		private var mapWindowHeight:Number;				
		public static var mapAlpha:Number;		
		private var mapVisible:Boolean;  				
		
		private var verticalAlign:String;				
		private var horizontalAlign:String;
		private var margin:Array;				
		
		private var iconVisible:Boolean;  	
		private var comboBoxVisible:Boolean;  	
		public static var useLoadSpacePlus:Boolean;
		
		private var maps:Array;	
		
		private var universalMapIcon:Sprite;
		
		private var mapWindow:Sprite;			
		private var mapViewer:MapViewer;		
		private var comboBox:ComboBox;		
		private var closeIcon:Sprite;		
		
		private var currentSpace:String;
		private var camInit:Object;		
		
		private var mapChanged:Boolean;		
		private var spaceChanged:Boolean; //  change from "space.preview" to "space" is not a change
		private var firstBuild:Boolean;
				
		private var BroadcastEvent:Class;
		private var ModuleLoader:Class;               
        private var moduleLoader:Object;
				
		/**
		 * Constructor 
		 */
		public function UniversalMap() {
			if (stage) stageReady();
			else addEventListener(Event.ADDED_TO_STAGE, stageReady, false, 0, true);      
		}
		
		private function stageReady(e:Event = null):void {            
			removeEventListener(Event.ADDED_TO_STAGE, stageReady);					
			
			this.firstBuild = true;
									
			BroadcastEvent = ApplicationDomain.currentDomain.getDefinition("zephyr.BroadcastEvent") as Class;	
			ModuleLoader = ApplicationDomain.currentDomain.getDefinition("ModuleLoader") as Class;                       
            moduleLoader = ModuleLoader(parent); 			
						
			moduleLoader.addEventListener(BroadcastEvent.SPACE_LOADED, getCurrentSpace, false, 0, true);
			moduleLoader.addEventListener(BroadcastEvent.CAMERA_INIT, getCamInit, false, 0, true);
			moduleLoader.addEventListener(BroadcastEvent.ALL_LAYERS_LOADED, layersReady, false, 0, true);													
			moduleLoader.addEventListener(BroadcastEvent.UNIVERSALMAP_TOGGLE, toggleShowWindow, false, 0, true);
			
			// this is needed for keeping track on camera direction when switching between maps
			moduleLoader.addEventListener(BroadcastEvent.CAMERA_SPIN, camSpin, false, 0, true);	
			moduleLoader.addEventListener(BroadcastEvent.CAMERA_ZOOM, camZoom, false, 0, true);												
		}		
		
		// ALL_LAYERS_LOADED -> SPACE_LOADED -> CAMERA_INIT ->  on first load
		// SPACE_LOADED -> CAMERA_INIT otherwise

		private function getCurrentSpace(e:zephyr.BroadcastEvent):void {		
			//Trace.add("SPACE_LOADED");
			if(this.currentSpace){
				if (this.getMapLabelFromSpace(this.currentSpace) == this.getMapLabelFromSpace(e.info.spaceLoaded) || (this.getMapLabelFromSpace(e.info.spaceLoaded) == this.comboBox.getActive().label) ) {
					this.mapChanged = false					
				}else {
					this.mapChanged = true;					
				}					
				if (this.currentSpace == e.info.spaceLoaded + ".preview") {
					this.spaceChanged=false
				}else {
					this.spaceChanged = true;
				}				
			}else {
				this.spaceChanged = true;
				this.mapChanged = true;												
			}
			this.currentSpace = e.info.spaceLoaded;				
			
		}								
		
		private function getCamInit(e:zephyr.BroadcastEvent):void {									
			//Trace.add("CAMERA_INIT");			
			this.camInit = e.info;
			if(this.spaceChanged){
				this.buildMap();																	
			}			
		}				
		
		private function layersReady(e:zephyr.BroadcastEvent):void {	
			//Trace.add("ALL_LAYERS_LOADED");
			this.getSettings();			
			this.buildAll();						
			this.handleStageResize();			
		}		
		
		private function camSpin(e:zephyr.BroadcastEvent):void {			
			this.camInit.pan=e.info.spin.split(",")[1]; 
			this.camInit.tilt=e.info.spin.split(",")[0]; 
		}
		
		private function camZoom(e:zephyr.BroadcastEvent):void {
			this.camInit.zoom=e.info.zoom;
		}
		
		/**
		 * Reads general plugin settings specified in xml file		  
		 * Builds array of maps each containing array of navigation points
		 */
		private function getSettings():void {									
			var settings:XML = moduleLoader.xmlByName["UniversalMap"];           			
									
			// setting map window size, definig default size if attributes not present			
			this.mapWindowWidth =  (settings.@mapWindowWidth != undefined) ?  Number(settings.@mapWindowWidth) : 400 ;
			this.mapWindowHeight = (settings.@mapWindowHeight != undefined) ?  Number(settings.@mapWindowHeight) : 300 ;			
			mapAlpha = (settings.@mapAlpha != undefined) ?  Number(settings.@mapAlpha) : 1 ;			
			this.mapVisible = (settings.@initailVisibility != undefined) ?  settings.@initailVisibility == "true" : true ;	
			
			this.verticalAlign = (settings.@verticalAlign != undefined) ?  settings.@verticalAlign : "center" ;
			this.horizontalAlign = (settings.@horizontalAlign != undefined) ?  settings.@horizontalAlign : "center" ;
			this.margin = (settings.@margin != undefined) ? String(settings.@margin).split(",")  : new Array(0, 0, 0, 0) ;			
			
			this.iconVisible = (settings.@iconVisible != undefined) ?  settings.@iconVisible == "true" : true ;	
			this.comboBoxVisible = (settings.@comboBoxVisible != undefined) ?  settings.@comboBoxVisible == "true" : true ;	
			useLoadSpacePlus = (settings.@useLoadSpacePlus != undefined) ? settings.@useLoadSpacePlus == "true" : false;
						
			// building array of maps
			this.maps = new Array();			
			for each(var xmlMap:XML in settings.elements()) {	
				
				// setting single map parameters, defining default values
				var mapLabel:String =       (xmlMap.@label != undefined) ? String(xmlMap.@label) : "label_empty_"+xmlMap.childIndex();
				var mapUrl:String   =       (xmlMap.@url   != undefined) ? String(xmlMap.@url) : "_empty";
				var mapAllowZoom:Boolean  = (xmlMap.@url   != undefined) ? xmlMap.@allowZoom=="true" : false;
				
				// building array of points for each map
				var navigationPoints:Array = new Array();				
				for each(var xmlPoint:XML in xmlMap.elements()) {									
					
					navigationPoints.push(new NavigationPoint(				
						Number((xmlPoint.@x != undefined) ? xmlPoint.@x : 0),
						Number((xmlPoint.@y != undefined) ? xmlPoint.@y : 0),
						Number((xmlPoint.@panShift != undefined) ? xmlPoint.@panShift : 0),
						String((xmlPoint.@target != undefined) ? xmlPoint.@target : "_empty"),
						String((xmlPoint.@type != undefined) ? xmlPoint.@type : ((xmlMap.@type != undefined) ? xmlMap.@type : "Cam_default")),
						this.validateSize(Number((xmlPoint.@size != undefined) ? xmlPoint.@size : ((xmlMap.@size != undefined) ? xmlMap.@size : 100))),
						this.validateColor(String((xmlPoint.@color != undefined) ? xmlPoint.@color : ((xmlMap.@color != undefined) ? xmlMap.@color : "#00FF00")))
					));		
					
				}						
				maps.push(new Map(mapLabel, mapUrl, navigationPoints, mapAllowZoom));		
			}
		}	
		
		private function validateSize(size:Number):Number {
			return (Math.abs(size/100) <= 1.6) ? Math.abs(size/100) : 1.6 ;
		}		
		private function validateColor(color:String):String {
			return  "0x"+color.substring( 1 ) ;
		}		
		
		/**
		 * Places map elements inside map window
		 */
		private function buildAll():void {						
						
			// adds icon to upper right corner of the application			
			if (this.iconVisible) {
				this.universalMapIcon = new Sprite();			
				var u_i:Bitmap = new Bitmap(new Bitmap_universalMapIcon().bitmapData);
				this.universalMapIcon.addChild(u_i);
				this.universalMapIcon.x = this.stage.stageWidth - this.universalMapIcon.width;
				this.universalMapIcon.addEventListener(MouseEvent.CLICK, toggleShowWindow);
				this.universalMapIcon.buttonMode = true;						
				this.universalMapIcon.alpha = 0; // initially hidden
				this.universalMapIcon.visible = false;								
				this.addChild(universalMapIcon);
			}			
			
			// draw map window
			this.mapWindow = new Sprite();			
			this.mapWindow.graphics.beginFill(0xFFFFFF);
			this.mapWindow.graphics.drawRect(0, 0, this.mapWindowWidth, this.mapWindowHeight);			
			this.mapWindow.graphics.endFill();																					
			this.mapWindow.alpha = 0; 
			this.mapWindow.visible = false; // initially hidden			
			this.addChild(this.mapWindow);									
									
			// draw combobox			
			var labels:Array = new Array();			
			for each (var map:Map in maps) {
				labels.push({label:map.label});
			}			
			var comboBoxStyle:ComboBoxStyle = new ComboBoxStyle();
			comboBoxStyle.initiallyBlank = true;			
			comboBoxStyle.elementSpacing = -1;
			comboBoxStyle.elementAlpha = 1 / mapAlpha;
			comboBoxStyle.mainElementAlpha = 1 / mapAlpha;
			comboBoxStyle.buttonTriangleColor = 0xcacaca;
			comboBoxStyle.buttonBackgroundColor = 0xf5f5f5;
			this.comboBox = new ComboBox(labels,comboBoxStyle);								
			this.comboBox.visible = this.comboBoxVisible;			
			this.comboBox.x = this.comboBox.y = 4; // initial position before map is loaded
			this.mapWindow.addChild(this.comboBox);					
			this.mapWindow.addEventListener(ComboBoxEvent.LABEL_CHANGED, comboBoxLabelChanged);			
			
			// draw close icon
			this.closeIcon = new Sprite();
			var c_i:Bitmap = new Bitmap(new Bitmap_universalMapClose().bitmapData);
			this.closeIcon.addChild(c_i);
			this.closeIcon.addEventListener(MouseEvent.CLICK, toggleShowWindow);
			this.closeIcon.buttonMode = true;			
			this.closeIcon.x = this.mapWindow.width - this.closeIcon.width -4;	// right upper corner of map window		
			this.closeIcon.y = 4;
			this.closeIcon.alpha = 1/mapAlpha; // so it wont be ever transparent
			this.mapWindow.addChild(this.closeIcon);
			
			
			// reposition map window after fullscreen 
			this.stage.addEventListener(Event.RESIZE, this.handleStageResize); 		   			
		}				
		
		/**
		 * Function is casted after camera initiation, so works either for navigation points inside map
		 * and navigation from auter apllication
		 * @param e
		 */		
		private function buildMap(label:String=null):void {				
			
			// properties of initiated camera that are passed down to navigation point			
			var params:NavigationPointParameters = new NavigationPointParameters(this.currentSpace, this.camInit.pan, this.camInit.tilt, this.camInit.zoom);						
			var newMap:String = this.getMapLabelFromSpace(this.currentSpace);			
						
			if (this.firstBuild) {												
				this.mapViewer = new MapViewer(this.getMapfromLabel(newMap), this.mapWindowWidth, this.mapWindowHeight, params);										
				this.mapWindow.addChild(this.mapViewer);								
				this.mapWindow.setChildIndex(this.mapViewer, 0); 
				this.comboBox.setSelected(newMap); 								
								
			}			
			
			// go to map of specified label
			else if (label) {				
				this.mapViewer.changeMap(this.getMapfromLabel(label), params);																				
			}
			
			// go to different navigation point in the same map
			else if(!this.mapChanged){
				this.mapViewer.updateNavigationPoints(params);				
			}
			
			// go to navigation point on diferent map
			else {												
				this.mapViewer.changeMap(this.getMapfromLabel(newMap), params);
				this.comboBox.setSelected(newMap); // does not cast comboBoxLabelChanged								
			}
			
			this.mapChanged = false;
		}	
		
		/**
		 * Casted whenever combobox is clicked 
		 * @param	e
		 */
		private function comboBoxLabelChanged(e:ComboBoxEvent):void {			
			this.buildMap(e.info.label);			
		}		
		
		/**
		 * Refreshes map position after enetering and leaving fullscreen
		 * @param	e
		 */
		private function handleStageResize(e:Event = null):void {      
			
			var resultX:Number =0;
			var resultY:Number =0;
			
			switch (this.horizontalAlign) {
				case "left": {
					resultX = 0;	
				}break;
				case "right": {
					resultX = this.stage.stageWidth - this.mapWindowWidth;
				}break;				
				default: { // "center"
					resultX = this.stage.stageWidth * 0.5 - this.mapWindowWidth * 0.5;
				}
			}			
			
			resultX -= Number(this.margin[1])// move right
			resultX += Number(this.margin[3])// move left
			
			switch (this.verticalAlign) {
				case "top": {
					resultY = 0;
				}break;
				case "bottom": {
					resultY = this.stage.stageHeight - this.mapWindowHeight;
				}break;				
				default: { // "center"
					resultY = this.stage.stageHeight * 0.5 - this.mapWindowHeight * 0.5;
				}
			}
			
			resultY += Number(this.margin[0])// move up
			resultY -= Number(this.margin[2])// move down
						
			this.mapWindow.x = resultX;
			this.mapWindow.y = resultY;

			if(this.iconVisible){
				this.universalMapIcon.x = this.stage.stageWidth - this.universalMapIcon.width;		
			}
		}
		
		/**
		 * Toggles showing / hiding of map window
		 * @param e
		 */
		private function toggleShowWindow(e:Event = null): void {	
			var windowsSpeed:Number = 0.5;
			TweenLite.killDelayedCallsTo(toggleShowWindowConfirm);
			TweenLite.delayedCall(windowsSpeed, toggleShowWindowConfirm, [this.mapVisible]); 
			if (this.mapVisible) {				
				if(this.iconVisible){ 
					this.universalMapIcon.visible = true;
					TweenLite.to(this.universalMapIcon, windowsSpeed, { alpha: 1 } );
				}
				TweenLite.to(this.mapWindow, windowsSpeed, { alpha: 0 } );						
			}else {				
				this.mapWindow.visible = true;
				if(this.iconVisible){ 
					TweenLite.to(this.universalMapIcon, windowsSpeed, { alpha: 0 } );
				}
				TweenLite.to(this.mapWindow, windowsSpeed, { alpha: mapAlpha } );								
			}
			moduleLoader.dispatchEvent(new BroadcastEvent(BroadcastEvent.UNIVERSALMAP_CHANGED, {visible:!this.mapVisible}));
		}				
		
		private function toggleShowWindowConfirm(visible:Boolean):void {
			if (visible) {
				this.mapWindow.visible = false;
				this.mapVisible = false;				
			}else {				
				if(this.iconVisible){ 
					this.universalMapIcon.visible = false;
				}
				this.mapVisible = true;				
			}							
		}		
		
		public function firstShowWindow():void {			
			if (this.firstBuild && this.mapVisible) {					
				this.mapVisible = false;
				this.toggleShowWindow();					
			}	
			this.firstBuild = false;
		}
		
		/**
		 * Serches navigation points in each map for specified targetspace
		 * @param space
		 * @return
		 */
		private function getMapLabelFromSpace(space:String):String {
			for each(var map:Map in maps) {
				for each(var point:NavigationPoint in map.navigationPoints) {					
					if ((point.targetSpace == space) || (point.targetSpace == space+".preview") || (point.targetSpace+".preview" == space)){
						return map.label;
					}
				}
			}				
			return Map(maps[0]).label;			
		}
		
		/**
		 * Returns map of specified label
		 * @param label
		 * @return
		 */
		private function getMapfromLabel(label:String):Map {
			for each(var map:Map in maps) {
				if (map.label == label) {
					return map;
				}
			}	
			return maps[0];			
		}		
		
		public function placeComboBox():void {
			this.comboBox.y = 4;
			this.comboBox.x = (this.mapViewer.getDragActive()) ? this.mapViewer.getNavigationWidth() + 10  : 4 ;
		}		
	}
}
